pub mod batch;
pub mod board;
pub mod framework;
pub mod sprite;
pub mod tetris;

use cgmath::{vec2, vec3, vec4, Matrix4, Vector2, Vector4};
use image::GenericImageView;
use winit::{dpi::PhysicalSize, window::Window};

use crate::math::Camera;

use self::{
    batch::RectBatch,
    framework::{
        buffer::UniformBuffer,
        texture::{Pixel, Pixmap},
        Context,
    },
};

const fn get_val_color(val: u8) -> Vector4<f32> {
    match val {
        1 => vec4(1.0, 0.0, 0.0, 1.0),
        2 => vec4(1.0, 1.0, 0.0, 1.0),
        3 => vec4(0.0, 1.0, 0.0, 1.0),
        4 => vec4(0.0, 1.0, 1.0, 1.0),
        5 => vec4(0.0, 0.0, 1.0, 1.0),
        6 => vec4(1.0, 0.0, 1.0, 1.0),
        _ => vec4(0.01, 0.01, 0.01, 1.0),
    }
}

impl AsBytes for Matrix4<f32> {}

const LOGICAL_WIDTH: f32 = 40.0;
const QUAD_SIZE: Vector2<f32> = vec2(1.0, 1.0);

pub struct Renderer<'win> {
    context: Context<'win>,
    camera: Camera,
    global: UniformBuffer<Matrix4<f32>>,
    pub tetris: tetris::Renderer,
    pub board: board::Renderer,
    pub next: tetris::Renderer,
    pub next_label: sprite::Renderer,
    pub batch: RectBatch,
}

impl<'win> Renderer<'win> {
    pub fn new(window: &'win Window) -> Self {
        let PhysicalSize { width, height } = window.inner_size();
        let context = Context::new(window, width, height);
        let global = UniformBuffer::new(&context, wgpu::ShaderStages::VERTEX);
        let tetris = tetris::Renderer::new(&context, global.layout());
        let aspect = height as f32 / width as f32;

        let img = image::load_from_memory(include_bytes!("../../assets/next.png")).unwrap();
        let (img_w, img_h) = img.dimensions();

        let pixmap = Pixmap::from_pixels(
            img.to_rgba8().pixels().map(|p| Pixel::from_rgba_array(p.0)),
            img_w,
            img_h,
        );

        let mut bg = sprite::Renderer::new(&context, global.layout(), pixmap);
        bg.transform.position = vec3(25.0, 35.0, 0.0);
        bg.set_scale(2.0);

        let batch = RectBatch::new(&context, global.layout());
        Self {
            camera: Camera::from_ortho(LOGICAL_WIDTH, LOGICAL_WIDTH * aspect),
            next: tetris::Renderer::with_pipeline(&context, tetris.material.pipeline()),
            board: board::Renderer::new(&context, global.layout()),
            tetris,
            global,
            context,
            next_label: bg,
            batch,
        }
    }

    pub fn resize(&mut self, width: u32, height: u32) {
        self.context.resize(width, height);
        let aspect = height as f32 / width as f32;
        self.camera.set_ortho(LOGICAL_WIDTH, LOGICAL_WIDTH * aspect);
    }

    fn prepare(&mut self) {
        self.global.write(&self.context, &self.camera.combination());
        self.board.prepare(&self.context);
        self.tetris.prepare(&self.context);
        self.next.prepare(&self.context);
        self.next_label.prepare(&self.context);
        self.batch.prepare(&self.context);
    }

    pub fn render(&mut self) {
        self.prepare();

        let surf_tex = self.context.surface.get_current_texture().unwrap();
        let view = surf_tex.texture.create_view(&wgpu::TextureViewDescriptor {
            label: None,
            format: Some(self.context.config.format),
            dimension: Some(wgpu::TextureViewDimension::D2),
            ..Default::default()
        });

        let mut encoder = self.context.create_command_encoder();
        {
            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: None,
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: &view,
                    resolve_target: None,
                    ops: wgpu::Operations {
                        load: wgpu::LoadOp::Clear(wgpu::Color {
                            r: 0.1,
                            g: 0.1,
                            b: 0.1,
                            a: 1.0,
                        }),
                        store: wgpu::StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: None,
                timestamp_writes: None,
                occlusion_query_set: None,
            });

            pass.set_bind_group(0, self.global.bind_group(), &[]);
            self.board.render(&mut pass);
            self.tetris.render(&mut pass);
            self.batch.render(&mut pass);
            self.next.render(&mut pass);
            self.next_label.render(&mut pass);
        }

        self.context.queue.submit([encoder.finish()]);
        surf_tex.present();
    }
}

pub trait AsBytes {
    fn as_bytes(&self) -> &[u8]
    where
        Self: Sized,
    {
        unsafe {
            std::slice::from_raw_parts(
                self as *const Self as *const u8,
                std::mem::size_of::<Self>(),
            )
        }
    }
}

impl<D: AsBytes> AsBytes for &[D] {
    fn as_bytes(&self) -> &[u8] {
        unsafe {
            std::slice::from_raw_parts(self.as_ptr() as _, std::mem::size_of::<D>() * self.len())
        }
    }
}

impl AsBytes for u32 {}

pub trait Vertex: AsBytes {
    const ATTRS: &'static [wgpu::VertexAttribute];
    fn layout() -> wgpu::VertexBufferLayout<'static>
    where
        Self: Sized,
    {
        wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<Self>() as u64,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: Self::ATTRS,
        }
    }
}
